package edu.washington.cs.workflow.analysis;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import javax.swing.event.ListSelectionEvent;

import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.CallGraph;
import com.ibm.wala.ipa.callgraph.propagation.AllocationSiteInNode;
import com.ibm.wala.ipa.callgraph.propagation.ConcreteTypeKey;
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.ipa.callgraph.propagation.LocalPointerKey;
import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis;
import com.ibm.wala.ipa.callgraph.propagation.PointerKey;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.types.TypeName;

import edu.washington.cs.workflow.util.Utils;
import edu.washington.cs.workflow.util.WALAUtils;

/**
 * Input: the signature of an event handling method.
 *        the computed call graph of the program
 *        the computed pointer analysis of the program
 * Output: output a list of possible GUI objects that
 *        can trigger this event handling method. 
 * */
@Deprecated
public class GUIObjectInitFinder {
	
	public final CallGraph cg;
	public final PointerAnalysis pa;
	public final String[] appPackages;
	
	private EventHandlerManager eventManager = new EventHandlerManager();
	private GUILabelIdentifier labelIdentifier = new GUILabelIdentifier();
	
	public GUIObjectInitFinder(AnalysisBuilder builder) {
		this(builder.getCallGraph(), builder.getPointerAnalysis(), builder.appPackages);
	}
	
	public GUIObjectInitFinder(CallGraph cg, PointerAnalysis pa,
			String[] appPackages) {
		this.cg = cg;
		this.pa = pa;
		Utils.checkNoNull(appPackages);
		this.appPackages = appPackages;
	}
	
	//XXX may have redundant
	
	/**
	 * The full name of class that defines the event handling method
	 * */
	public List<GUIObjectInitInfo> findGUIObjects(String classFullName) {
		IClass clazz = WALAUtils.findClassByName(classFullName, cg);
		Utils.checkNotNull(clazz, "class not found: " + classFullName);
		Utils.debugPrintln("found class: " + clazz);

		//all local vars that " var = new classFullName() ", "=" means points-to
		
		Collection<PointerKey> localVars = getAllocationSites(pa, clazz);
		Utils.debugPrintln("Number of local vars: " + localVars.size());

		List<GUIObjectInitInfo> retList = new LinkedList<GUIObjectInitInfo>();
		for (PointerKey localVar : localVars) {
			Utils.checkNotNull(localVar);
			Utils.checkTrue(localVar instanceof LocalPointerKey,
					"Not a LocalPointerKey type. It is: " + localVar.getClass());
			LocalPointerKey lpKey = (LocalPointerKey) localVar;
			//find the event-triggering object info
			Collection<GUIObjectInitInfo> objects
			    = this.findEventTriggeringGUIObjects(lpKey.getNode(), lpKey.getValueNumber());
			Utils.checkNotNull(objects);
			retList.addAll(objects);
		}

		return retList;
	}
	
	/**
	 * Given a node, and a value num; find the GUI init info
	 * */
	public List<GUIObjectInitInfo> findGUIObjects(CGNode node, int valueNum) {
		List<GUIObjectInitInfo> guiObjList = new LinkedList<GUIObjectInitInfo>();
		guiObjList.addAll(this.findEventTriggeringGUIObjects(node, valueNum));
		return guiObjList;
	}
	
	
	/**
	 * Returns a set of pointer keys, each of which points to the allocation sites
	 * of the given class.
	 * */
	private Collection<PointerKey> getAllocationSites(PointerAnalysis pa, IClass clazz) {
		Collection<PointerKey> keys = new LinkedList<PointerKey>();
		for(PointerKey key : pa.getPointerKeys()) {
			if(key.toString().indexOf("example/swing/HelloWorldFrame$1") != -1) {
				Utils.debugPrintln(key + ",  type: " + key.getClass());
				Utils.debugPrintln("   points to instance: " + pa.getPointsToSet(key).size());
				for(InstanceKey ik : pa.getPointsToSet(key)) {
					if(ik.toString().indexOf("example/swing/HelloWorldFrame$1") != -1) {
						Utils.debugPrintln("     - " + ik + ", t: " + ik.getClass());
					}
				}
			}
			if(includePointerKeyAsAllocationSite(pa, key, clazz)) {
				keys.add(key);
				Utils.debugPrintln(" * included" );
			}
			//XXX what is the difference between this and allocation site?
			if(includePointerKeyAsConcreteType(pa, key, clazz)) {
				keys.add(key);
				Utils.debugPrintln(" -- included" );
			}
		}
		return keys;
	}
	
	private boolean includePointerKeyAsConcreteType(PointerAnalysis pa, PointerKey key, IClass clazz) {
		if(!(key instanceof LocalPointerKey)) {
			return false;
		}
		for(InstanceKey instanceKey : pa.getPointsToSet(key)) {
			//check it is the allocation site
			if(instanceKey instanceof ConcreteTypeKey) {
				ConcreteTypeKey concreteType = (ConcreteTypeKey)instanceKey;
				TypeName tName = concreteType.getConcreteType().getName();
//					allocSite.getSite().getDeclaredType().getName();
				//check the type name, allocate the given class?
				if(!tName.equals(clazz.getName())) {
					continue;
				}
				//check it is in the same package
				LocalPointerKey lpKey = (LocalPointerKey)key;
				CGNode hostNode = lpKey.getNode();
			    String pkgName = WALAUtils.getJavaPackageName(hostNode.getMethod().getDeclaringClass()); 
			    if(Utils.startWith(pkgName, this.appPackages)) {
			    	return true;
			    }
			}
		}
		return false;
	}
	/**
	 * For each pointer key, should it be included. Need to check whether it is an allocation
	 * site, and whether it is in the user package, allocate the given class instance, and it
	 * is a local key.
	 * */
	private boolean includePointerKeyAsAllocationSite(PointerAnalysis pa, PointerKey key, IClass clazz) {
		//check is it a local key
		if(!(key instanceof LocalPointerKey)) {
			return false;
		}
//		System.out.println("size: " + pa.getPointsToSet(key).size());
		for(InstanceKey instanceKey : pa.getPointsToSet(key)) {
			//check it is the allocation site
			if(instanceKey instanceof AllocationSiteInNode) {
				AllocationSiteInNode allocSite = (AllocationSiteInNode)instanceKey;
				TypeName tName = allocSite.getSite().getDeclaredType().getName();
				//check the type name, allocate the given class?
				if(!tName.equals(clazz.getName())) {
					continue;
				}
				//check it is in the same package
				LocalPointerKey lpKey = (LocalPointerKey)key;
				CGNode hostNode = lpKey.getNode();
			    String pkgName = WALAUtils.getJavaPackageName(hostNode.getMethod().getDeclaringClass()); 
			    if(Utils.startWith(pkgName, this.appPackages)) {
			    	return true;
			    }
			}
		}
		return false;
	}
	
	/**
	 * Given a call graph node, and a variable that represents an instance of an event-handling
	 * object. Find all objects that add this "event-handling object" as a listener object.
	 * This works in an intra-procedural way.
	 * */
	public Collection<GUIObjectInitInfo> findEventTriggeringGUIObjects(CGNode node, int valueNum) {
		Collection<GUIObjectInitInfo> retObjs = new LinkedList<GUIObjectInitInfo>();
		List<SSAInstruction> irList = WALAUtils.getAllIRs(node);
		for(SSAInstruction ssa : irList) {
			//interested in the invocation instruction
			if(ssa instanceof SSAInvokeInstruction) {
				SSAInvokeInstruction invoke = (SSAInvokeInstruction)ssa;
				if(!this.eventManager.isEventHandlerAdditionCall(invoke)) { //e.g., addActionListener
					continue;
				}
				if(useVarInInstruction(valueNum, invoke)) {
					//e.g., return use(0) for instruction like: jbutton.addActionListener
					int guiObjVarIndex = this.labelIdentifier.getGUIIndexInActionAdding(invoke);
					//look for the method call like: <init> or setLabel which may contain label data
					for(SSAInstruction instr : irList) {
						if(!(instr instanceof SSAInvokeInstruction)
								|| instr.getNumberOfUses() == 0) {
							continue;
						}
						SSAInvokeInstruction labelSetInstr = (SSAInvokeInstruction)instr;
						if(!this.labelIdentifier.isLabelSettingMethod(labelSetInstr)) {
							continue;
						}
						//make sure the correct var
						if(this.labelIdentifier.getGUIIndexInLabelSetting(labelSetInstr) == guiObjVarIndex) {
							int labelVarIndex = this.labelIdentifier.getLabelVarIndex(labelSetInstr);
							String label = WALAUtils.getVarValue(node, labelVarIndex);
							//create the gui object info
							String typeName = getReceiverObjTypeName(labelSetInstr);
							GUIObjectInitInfo objInfo = new GUIObjectInitInfo(label, typeName , node);
							retObjs.add(objInfo);
						}
					}
				}
			}
		}
		
		return retObjs;
	}
	
	/**
	 * returns button for button.<init>
	 * */
	public static String getReceiverObjTypeName(SSAInvokeInstruction instr) {
		Utils.checkTrue(!instr.isStatic());
		Utils.checkTrue(instr.getNumberOfUses() > 0);
		return instr.getDeclaredTarget().getDeclaringClass().getName().toString();
	}
	
	public static boolean useVarInInstruction(int valueNum, SSAInstruction ssa) {
		for(int i = 0; i < ssa.getNumberOfUses(); i++) {
			if(ssa.getUse(i) == valueNum) {
				return true;
			}
		}
		return false;
	}
}
